package multithreading;

/**
 * 线程同步
 * 语法：
 *      synchronized(){
 * 	        // 线程同步代码块
 *      }
 * synchronized后面小括号()中传的这个“数据”是相当关键的。这个数据必须是多线程共享的数据，才能达到多线程排队
 * 假设t1、t2、t3、t4、t5，有5个线程，只希望t1、t2、t3排队，t4、t5不需要排队。怎么办？
 * 一定要在()中写一个t1、t2、t3共享的对象。而这个对象对于t4、t5来说不是共享的
 * 这里的共享对象是：账户对象
 * 账户对象是共享的，那么this就是账户对象！！！
 * ()不一定是this，这里只要是多线程共享的那个对象就行
 *
 * 在java语言中，任何一个对象都有“一把锁”，其实这把锁就是标记。（只是把它叫做锁）
 *
 * 也可以在实例方法上使用synchronized，此时锁一定是this，也只能是this，这种方式不灵活，不常用
 * synchronized出现在实例方法上，表示整个方法体都需要同步，可能会无故扩大同步的范围，导致程序的执行效率降低
 * public synchronized void method(double parameter) { }
 *
 * 在静态方法上使用synchronized
 * 表示找类锁，类锁永远只有1把
 *
 * 对象锁：1个对象1把锁，100个对象100把锁
 * 类锁：100个对象，也只能是1把类锁
 *
 * synchronized会让程序的执行效率降低，用户体验不好
 * 系统的用户吞吐量降低。用户体验差。在不得已的情况下再选择线程同步机制
 * */
public class ThreadSynchronizedDemo {
    public static void main(String[] args) {
        // 创建账户对象（只创建1个）
        Account act = new Account("act-001", 10000);
        // 创建两个线程，共享同一个对象
        Thread t1 = new AccountThread(act);
        Thread t2 = new AccountThread(act);

        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        t2.start();
    }
}

class Account {
    private String actno;
    private double balance; //实例变量
    Object o = new Object(); // 实例变量
    // （Account对象是多线程共享的，Account对象中的实例变量obj也是共享的）

    public Account() {
    }

    public Account(String actno, double balance) {
        this.actno = actno;
        this.balance = balance;
    }

    public String getActno() {
        return actno;
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    //取款的方法
    public void withdraw(double money) {
        /**
         * 以下可以共享,金额不会出错
         * 以下这几行代码必须是线程排队的，不能并发。
         * 一个线程把这里的代码全部执行结束之后，另一个线程才能进来。
         */
        synchronized (this) {
            //synchronized(actno) {
            //synchronized(o) {

            /**
             * 以下不共享，金额会出错
             */
		  /*Object obj = new Object();
	        synchronized(obj) { // 这样编写就不安全了。因为obj2不是共享对象。
	        synchronized(null) {//编译不通过
	        String s = null;
	        synchronized(s) {//java.lang.NullPointerException*/
            double before = this.getBalance();
            double after = before - money;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setBalance(after);
            //}
        }
    }
}

class AccountThread extends Thread {
    // 两个线程必须共享同一个账户对象。
    private Account act;

    // 通过构造方法传递过来账户对象
    public AccountThread(Account act) {
        this.act = act;
    }

    public void run(){
        double money = 5000;
        act.withdraw(money);
        System.out.println(Thread.currentThread().getName() + "对"+act.getActno()+"取款"+money+"成功，余额" + act.getBalance());
    }
}
